/**
 * 
 */
package com.tiduyun.data.exchange.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;
import javax.net.ssl.SSLException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;

import com.tiduyun.data.exchange.constant.ExceptionCode;
import com.tiduyun.data.exchange.exception.CodedRuntimeException;
import com.tiduyun.data.exchange.service.GxVehicleDataAdapterService;
import com.tiduyun.data.exchange.util.HttpClientUtils;
import com.tiduyun.data.exchange.util.HttpUrlUtils;
import com.tiduyun.data.exchange.util.JSONUtils;
import com.tiduyun.data.exchange.util.ParamCheckUtils;
import com.tiduyun.data.exchange.util.http.BaseHttpApiClient;
import com.tiduyun.data.exchange.util.http.UnexpectedStatusCodeException;
import com.tiduyun.data.exchange.util.http.config.CmpHttpClientBuilderProperties;
import com.tiduyun.data.exchange.util.http.config.CmpHttpClientRequestConfigProperties;

import lombok.extern.slf4j.Slf4j;

/**
 * @author wuzheng@tiduyun.com
 *
 */
@Service("gxVehicleDataAdapterServiceImpl")
@Slf4j
public class GxVehicleDataAdapterServiceImpl extends BaseHttpApiClient implements GxVehicleDataAdapterService {

    protected static class GxVehicleHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {

        public GxVehicleHttpRequestRetryHandler() {
            super();
        }

        public GxVehicleHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled,
                Collection<Class<? extends IOException>> clazzes) {
            super(retryCount, requestSentRetryEnabled, clazzes);
        }

        public GxVehicleHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled) {
            super(retryCount, requestSentRetryEnabled);
        }

    }

    private String token;

    private long tokenExpireAt = 0;

    private Object tokenRefreshLockObj = new Object();

    @Value("${td-data.gx-vehicle.mock-host:}")
    private String mockHost;

    @Value("${td-data.gx-vehicle.get-token.remote-url}")
    private String getTokenRemoteUrl;

    @Value("${td-data.gx-vehicle.get-token.grant-type}")
    private String getTokenGrantType;

    @Value("${td-data.gx-vehicle.get-token.client-id}")
    private String getTokenClientId;

    @Value("${td-data.gx-vehicle.get-token.client-secret}")
    private String getTokenClientSecret;

    @Value("${td-data.gx-vehicle.get-vehicle-info.remote-url}")
    private String getVehicleInfoRemoteUrl;

    @Value("${td-data.gx-vehicle.get-vehicle-info.org-no}")
    private String getVehicleInfoOrgNo;

    @Value("${td-data.gx-vehicle.get-vehicle-info.ip}")
    private String getVehicleInfoIp;

    @Value("${td-data.gx-vehicle.check-vehicle-invoice.remote-url}")
    private String checkVehicleInvoiceRemoteUrl;

    @Value("${td-data.gx-vehicle.check-vehicle-invoice.token-id}")
    private String checkVehicleInvoiceTokenId;

    @Value("${td-data.gx-vehicle.data-dict-file-path}")
    private String dataDictFilePath;

    private Map<String, String> vehicleTypeByHpzl;

    private Map<String, String> addressByAreaNo;

    @Autowired
    private CmpHttpClientBuilderProperties cmpHttpClientBuilderProperties;

    private Pattern ztmPattern = Pattern.compile("[\\{,]ztm:'(.+?)'", Pattern.MULTILINE);

    private Pattern kprqPattern = Pattern.compile("[\\{,]kprq:'(.+?)'", Pattern.MULTILINE);

    private Pattern swjgMcPattern = Pattern.compile("[\\{,]swjgMc:'(.+?)'", Pattern.MULTILINE);

    private Pattern kpsePattern = Pattern.compile("[\\{,]'kpse':'(.+?)'", Pattern.MULTILINE);

    private Pattern bhsjePattern = Pattern.compile("[\\{,]'bhsje':'(.+?)'", Pattern.MULTILINE);

    private Pattern xxfmcPattern = Pattern.compile("[\\{,]'xxfmc':'(.+?)'", Pattern.MULTILINE);

    private Pattern zcdxzqhPattern = Pattern.compile("[\\{,]'zcdxzqh':'(.+?)'", Pattern.MULTILINE);

    private Pattern skxzqhPattern = Pattern.compile("[\\{,]'skxzqh':'(.+?)'", Pattern.MULTILINE);

    private Pattern fpccArryPattern = Pattern.compile("[\\{,]fpccArry:'(.+?)'", Pattern.MULTILINE);

    private Pattern invalidTokenResponsePattern = Pattern.compile("\"error\":\"invalid_token\"", Pattern.MULTILINE);

    private List<String> remoteVehicleFields;

    private Map<String, String> resultFieldByRemoteField;

    private String getVehicleInfoFieldsClause;

    private HttpClientContext httpClientContext;

    @SuppressWarnings("unchecked")
    @PostConstruct
    public void initService() throws IOException {
        File dataDictFile = ResourceUtils.getFile(dataDictFilePath);
        String dataDictContent = FileUtils.readFileToString(dataDictFile, "UTF-8");
        Map<String, Object> dataDictJson = JSONUtils.parseMap(dataDictContent);

        // 车辆类型
        Map<String, Object> vehicleTypeMap = (Map<String, Object>) dataDictJson.get("vehicle-type-by-hpzl-code");
        vehicleTypeByHpzl = new HashMap<>();
        for (Map.Entry<String, Object> entry : vehicleTypeMap.entrySet()) {
            vehicleTypeByHpzl.put(entry.getKey(), entry.getValue().toString());
        }

        // 行政区划地址
        Map<String, Object> areaNoMap = (Map<String, Object>) dataDictJson.get("address-by-area-no");
        addressByAreaNo = new HashMap<>();
        for (Map.Entry<String, Object> entry : areaNoMap.entrySet()) {
            addressByAreaNo.put(entry.getKey(), entry.getValue().toString());
        }

        resultFieldByRemoteField = new HashMap<>();
        resultFieldByRemoteField.put("HPHM", "plate_no"); // 车辆号牌号码
        resultFieldByRemoteField.put("HPZL", "plate_type"); // 车辆号牌种类
        resultFieldByRemoteField.put("CLPP1", "brand_name"); // 车辆中文品牌
        resultFieldByRemoteField.put("CLXH", "veh_model"); // 车辆型号
        resultFieldByRemoteField.put("CLLX", "vehicle_type"); // 车辆类型
        resultFieldByRemoteField.put("CSYS_ZW", "veh_color"); // 车身颜色
        resultFieldByRemoteField.put("SFZMHM", "cert_no"); // 身份证号
        resultFieldByRemoteField.put("JDCSYR", "veh_owner_name"); // 机动车所有人
        resultFieldByRemoteField.put("CCDJRQ", "first_reg_date"); // 初次登记日期
        resultFieldByRemoteField.put("ZJDJRQ", "latest_check_date"); // 最近定检日期
        resultFieldByRemoteField.put("ZT_ZW", "veh_status"); // 机动车状态中文
        resultFieldByRemoteField.put("FDJH", "engine_no"); // 发动机号

        remoteVehicleFields = new ArrayList<>(resultFieldByRemoteField.keySet());

        StringBuilder getVehicleInfoFieldsClauseBuilder = new StringBuilder();
        for (String remoteVehicleField : remoteVehicleFields) {
            getVehicleInfoFieldsClauseBuilder.append("<Data>").append(remoteVehicleField).append("</Data>");
        }
        getVehicleInfoFieldsClause = getVehicleInfoFieldsClauseBuilder.toString();

        httpClientContext = createHttpClientContext();

        if (httpClientBuilder != null) {
            httpClientBuilder.setRetryHandler(new GxVehicleHttpRequestRetryHandler(1, false,
                    Arrays.asList(UnknownHostException.class, SSLException.class)));
        }
        initClient();
    }

    private HttpClientContext createHttpClientContext() {
        HttpClientContext httpClientContext = HttpClientContext.create();
        RequestConfig requestConfig = null;
        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
        CmpHttpClientRequestConfigProperties requestConfigProperties = cmpHttpClientBuilderProperties.getRequestConfig();
        if (requestConfigProperties != null) {
            requestConfigProperties.apply(requestConfigBuilder);
        } else {
            // 默认超时配置
            requestConfigBuilder.setConnectTimeout(40000).setConnectionRequestTimeout(5000).setSocketTimeout(30000);
        }
        requestConfig = requestConfigBuilder.build();
        httpClientContext.setRequestConfig(requestConfig);
        return httpClientContext;
    }

    /**
     * @see com.tiduyun.data.exchange.service.GxVehicleDataAdapterService#getVehicleInfo(java.util.Map)
     */
    @Override
    public Map<String, Object> getVehicleInfo(Map<String, Object> params) {
        String name = getRequiredStringParam(params, "veh_owner_name");
        String idNo = getRequiredStringParam(params, "cert_no");
        String carNo = getRequiredStringParam(params, "plate_no");
        String engineNo = (String) params.get("engine_no");

        name = StringUtils.trim(name);
        idNo = StringUtils.trim(idNo);
        carNo = StringUtils.trim(carNo);
        engineNo = StringUtils.trim(engineNo);

        if (carNo.length() >= 7) {
            carNo = carNo.substring(1);
        }
        carNo = carNo.trim().toUpperCase();
        boolean isFirstCh = true;
        for (char carNoCh : carNo.toCharArray()) {
            if (isFirstCh) {
                isFirstCh = false;
                continue;
            }
            if (!Character.isDigit(carNoCh) && !Character.isAlphabetic(carNoCh)) {
                log.info("Invalid car no: carNo={}", carNo);

                // 车牌号含有非法字符，只允许数字和字母
                Map<String, Object> result = new HashMap<>();
                result.put("biz_result", "FAIL");
                return result;
            }
        }

        idNo = idNo.trim().toUpperCase();
        char[] idNoChs = idNo.toCharArray();
        int idNoLength = idNoChs.length;
        if (idNoLength != 15 && idNoLength != 18) {
            log.info("Invalid id no: idNo={}", idNo);

            // 身份证号长度只允许15和18
            Map<String, Object> result = new HashMap<>();
            result.put("biz_result", "FAIL");
            return result;
        }
        for (int i = 0; i < idNoLength; ++i) {
            char idNoCh = idNoChs[i];
            if (!Character.isDigit(idNoCh)) {
                if (!(idNoCh == 'X' && i == 17)) {
                    log.info("Invalid id no: idNo={}", idNo);

                    // 身份证号只允许数字和字母，且只有18位长度的身份证号的最后一位允许字母
                    Map<String, Object> result = new HashMap<>();
                    result.put("biz_result", "FAIL");
                    return result;
                }
            }
        }

        String response = null;
        try {
            HttpApiRequest httpApiRequest = new HttpApiRequest();
            httpApiRequest.setUri(HttpUrlUtils.buildUri(getVehicleInfoRemoteUrl, null));
            httpApiRequest.setMethod("POST");
            httpApiRequest.setHttpClientContext(httpClientContext);

            List<Header> headers = new ArrayList<>();
            headers.add(HttpClientUtils.h("Content-Type", "application/json;charset=utf-8"));
            if (StringUtils.isNotEmpty(mockHost)) {
                headers.add(HttpClientUtils.h("Host", mockHost));
            }
            httpApiRequest.setHeaders(headers);

            List<NameValuePair> urlParams = HttpClientUtils.nameValuePairsOf("access_token", getToken());
            httpApiRequest.setUrlParams(urlParams);

            String bodyTemplate = "{\"params\":{\"ipadress\":\"%s\", \"reqstr\":\"<Items><Item>"
                    + "<Data>HPHM='%s'</Data><Data>1</Data><Data>20</Data><Data>0</Data>"
                    + "<Data>%s</Data><Data>%s</Data><Data>%s</Data></Item>" + "<Item><Row>%s</Row></Item></Items>\"}}";
            String body = String.format(bodyTemplate, getVehicleInfoIp, carNo, name, idNo, getVehicleInfoOrgNo,
                    getVehicleInfoFieldsClause);
            httpApiRequest.setBody(body);

            response = executeHttpApiRequest(httpApiRequest);
            if (hasInvalidTokenError(response)) {
                invalidateToken();
            }
        } catch (CodedRuntimeException e) {
            throw e;
        } catch (Exception e) {
            log.error("Get vehicle info failed", e);
            throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "invoke remote api failed");
        }

        try {
            return parseRemoteGetVehicleResult(response, name, idNo, carNo, engineNo);
        } catch (CodedRuntimeException e) {
            throw e;
        } catch (Exception e) {
            log.error("Parse vehicle info failed", e);
            throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "parse remote response error");
        }
    }

    protected Map<String, Object> parseRemoteGetVehicleResult(String response, String name, String idNo, String carNo,
            String engineNo) throws DocumentException {
        SAXReader reader = new SAXReader();
        reader.setEncoding("UTF-8");
        Document document = reader.read(new StringReader(response));
        Element root = document.getRootElement();
        Element methodEle = root.element("Method");
        Element itemsEle = methodEle.element("Items");
        Element itemEle = itemsEle.element("Item");
        Element itemValueEle = itemEle.element("Value");
        List<Element> rows = itemValueEle.elements("Row");

        Map<String, Object> result = new HashMap<>();

        Element statusRow = rows.get(0);
        Element statusCodeEle = statusRow.elements("Data").get(0);
        String statusCode = statusCodeEle.getTextTrim();
        if ("01".equals(statusCode)) {
            Map<String, Object> vehicleInfo = new HashMap<>();

            Element fieldRow = rows.get(1);
            List<Element> fieldNameEles = fieldRow.elements("Data");
            Map<Integer, String> remoteFiledByIndex = new HashMap<>();
            for (int i = 0; i < fieldNameEles.size(); ++i) {
                remoteFiledByIndex.put(i, fieldNameEles.get(i).getTextTrim());
            }

            if (rows.size() < 3) {
                log.error("Unexpect remote rows: rows={}", rows.size());
                throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "parse remote response failed");
            }

            // 如果发动机号非空，则只有发动机号匹配到数据时，才认为数据存在，否则认为数据不存在
            boolean engineNoIsEmpty = StringUtils.isEmpty(engineNo);
            boolean engineNoMatched = false;

            for (int i = 2; i < rows.size(); ++i) {
                Element valueRow = rows.get(i);
                List<Element> valueEles = valueRow.elements("Data");
                for (int j = 0; j < valueEles.size(); ++j) {
                    String remoteField = remoteFiledByIndex.get(j);
                    String resultField = resultFieldByRemoteField.get(remoteField);
                    if (resultField == null) {
                        log.error("Unexpect remote field: remoteField={}", remoteField);
                        throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "parse remote response failed");
                    }
                    Element valueEle = valueEles.get(j);
                    Element valueNormalEle = valueEle.element("Normal");
                    String value = valueNormalEle.getTextTrim();
                    vehicleInfo.put(resultField, value);
                }

                if (!engineNoIsEmpty) {
                    // 发动机号非空，进行发动机号匹配
                    String recordEngineNo = (String) vehicleInfo.get("engine_no");
                    engineNoMatched = engineNo.equalsIgnoreCase(recordEngineNo);

                    if (!engineNoMatched) {
                        // 发动机号不匹配，则认为记录不存在，继续向后进行探测
                        result.put("biz_result", "NOT_EXIST");
                        continue;
                    }
                }

                boolean paramMatched = true;

                String actualName = (String) vehicleInfo.get("veh_owner_name");
                if (!name.equalsIgnoreCase(actualName)) {
                    log.info("Vehicle info match failed by name: inputName={}, actualName={}", name, actualName);
                    paramMatched = false;
                }

                String actualIdNo = (String) vehicleInfo.get("cert_no");
                if (StringUtils.isEmpty(actualIdNo)) {
                    log.error("Unexpect cert no: cert_no={}", actualIdNo);
                    throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "parse remote response failed");
                }
                actualIdNo = actualIdNo.toUpperCase();
                vehicleInfo.put("cert_no", actualIdNo);
                if (!idNo.equalsIgnoreCase(actualIdNo)) {
                    log.info("Vehicle info match failed by id no: inputIdNo={}, actualIdNo={}", idNo, actualIdNo);
                    paramMatched = false;
                }

                if (paramMatched) {
                    String plateType = (String) vehicleInfo.get("plate_type");
                    // if (StringUtils.endsWith(plateType, "号牌")) {
                    // // 从号牌类型中解析车辆类型
                    // vehicleInfo.put("vehicle_type", plateType.substring(0,
                    // plateType.length() - 2));
                    // }
                    plateType = StringUtils.trim(plateType);
                    if (StringUtils.isNotEmpty(plateType)) {
                        // 通过号牌种类映射关系得到车辆类型
                        vehicleInfo.put("vehicle_type", vehicleTypeByHpzl.get(plateType));
                    }

                    String plateNo = (String) vehicleInfo.get("plate_no");
                    if (StringUtils.isNotEmpty(plateNo)) {
                        // 号牌长度大于6为绿牌，否则为蓝牌
                        if (plateNo.length() > 6) {
                            vehicleInfo.put("plate_type", "绿牌");
                        } else {
                            vehicleInfo.put("plate_type", "蓝牌");
                        }
                        vehicleInfo.put("plate_no", StringUtils.join("桂", plateNo));
                    }

                    String firstRegDate = (String) vehicleInfo.get("first_reg_date");
                    if (StringUtils.isNotEmpty(firstRegDate)) {
                        vehicleInfo.put("first_reg_date", StringUtils.substring(firstRegDate, 0, 10));
                    }

                    String latestCheckDate = (String) vehicleInfo.get("latest_check_date");
                    if (StringUtils.isNotEmpty(latestCheckDate)) {
                        vehicleInfo.put("latest_check_date", StringUtils.substring(latestCheckDate, 0, 10));
                    }

                    result.putAll(vehicleInfo);
                    result.put("biz_result", "EXIST");
                    break;
                } else {
                    result.put("biz_result", "FAIL");

                    if (!engineNoIsEmpty && engineNoMatched) {
                        // 发动机号非空且匹配成功，则说明记录存在但车主信息不匹配，不再探测后续记录
                        break;
                    }
                }
            }
        } else if ("02".equals(statusCode)) {
            result.put("biz_result", "NOT_EXIST");
        } else {
            log.info("Vehicle info match failed by response status code: statusCode={}", statusCode);
            throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "parse remote response failed");
        }
        return result;
    }

    /**
     * @see com.tiduyun.data.exchange.service.GxVehicleDataAdapterService#checkVehicleInvoice(java.util.Map)
     */
    @Override
    public Map<String, Object> checkVehicleInvoice(Map<String, Object> params) {
        String invoiceCode = getRequiredStringParam(params, "invoice_code");
        String invoiceNo = getRequiredStringParam(params, "invoice_no");
        String buyerName = (String) params.get("buyer_name");
        String buyerCertNo = (String) params.get("buyer_cert_no");
        String taxId = (String) params.get("tax_id");
        String vatInclusive = getRequiredStringParam(params, "vat_inclusive");
        String engineNo = (String) params.get("engine_no");
        String vin = getRequiredStringParam(params, "vin");

        invoiceCode = StringUtils.trim(invoiceCode);
        invoiceNo = StringUtils.trim(invoiceNo);
        buyerName = StringUtils.trim(buyerName);
        buyerCertNo = StringUtils.trim(buyerCertNo);
        taxId = StringUtils.trim(taxId);
        vatInclusive = StringUtils.trim(vatInclusive);
        engineNo = StringUtils.trim(engineNo);
        vin = StringUtils.trim(vin);

        // 移除小数点后末尾的0和小数点
        if (StringUtils.contains(vatInclusive, '.')) {
            while (vatInclusive.charAt(vatInclusive.length() - 1) == '0') {
                vatInclusive = vatInclusive.substring(0, vatInclusive.length() - 1);
            }
            if (vatInclusive.charAt(vatInclusive.length() - 1) == '.') {
                vatInclusive = vatInclusive.substring(0, vatInclusive.length() - 1);
            }
        }

        String response = null;
        try {
            HttpApiRequest httpApiRequest = new HttpApiRequest();
            httpApiRequest.setUri(HttpUrlUtils.buildUri(checkVehicleInvoiceRemoteUrl, null));
            httpApiRequest.setMethod("GET");
            httpApiRequest.setHttpClientContext(httpClientContext);

            List<Header> headers = new ArrayList<>();
            headers.add(HttpClientUtils.h("Content-Type", "application/json;charset=utf-8"));
            if (StringUtils.isNotEmpty(mockHost)) {
                headers.add(HttpClientUtils.h("Host", mockHost));
            }
            httpApiRequest.setHeaders(headers);

            List<NameValuePair> urlParams = new ArrayList<>();
            urlParams.add(HttpClientUtils.p("access_token", getToken()));
            urlParams.add(HttpClientUtils.p("fpdm", invoiceCode));
            urlParams.add(HttpClientUtils.p("fphm", invoiceNo));
            urlParams.add(HttpClientUtils.p("gfmc", buyerName));
            urlParams.add(HttpClientUtils.p("gfsfxjhm", buyerCertNo));
            urlParams.add(HttpClientUtils.p("nsrsbh", taxId));
            urlParams.add(HttpClientUtils.p("jsjg", vatInclusive));
            urlParams.add(HttpClientUtils.p("fdjhm", engineNo));
            urlParams.add(HttpClientUtils.p("cjhm", vin));
            urlParams.add(HttpClientUtils.p("tokenId", checkVehicleInvoiceTokenId));
            httpApiRequest.setUrlParams(urlParams);

            response = executeHttpApiRequest(httpApiRequest);
            if (hasInvalidTokenError(response)) {
                invalidateToken();
            }
        } catch (CodedRuntimeException e) {
            throw e;
        } catch (Exception e) {
            log.error("Request check vehicle invoice remote api failed", e);
            throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "invoke remote api failed");
        }

        return parseRemoteCheckVehicleInvoiceResult(response);
    }

    protected Map<String, Object> parseRemoteCheckVehicleInvoiceResult(String response) {
        try {
            String ztm = findValueByPattern(response, ztmPattern, 1);
            if (StringUtils.isEmpty(ztm)) {
                // 状态码获取失败
                log.error("Parse ztm from remote response failed");
                throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "read remote response failed:ztm error");
            }

            Map<String, Object> result = new HashMap<>();
            String bizResult = null;
            String invoiceStatus = null;
            String invoiceDate = null;
            String address = null;
            String kpse = null;
            String bhsje = null;
            String xxfmc = null;
            String zcdxzqh = null;
            String skxzqh = null;
            if ("0".equals(ztm) || "5".equals(ztm)) {
                bizResult = "EXIST";
                invoiceStatus = "正常";
            } else if ("2".equals(ztm)) {
                bizResult = "EXIST";
                invoiceStatus = "作废";
            } else if ("3".equals(ztm)) {
                bizResult = "EXIST";
                invoiceStatus = "红冲";
            } else if ("-1".equals(ztm)) {
                bizResult = "NOT_EXIST";
            } else if ("9".equals(ztm)) {
                bizResult = "FAIL";
                String code = null;
                String msg = null;
                String fpccArry = findValueByPattern(response, fpccArryPattern, 1);
                if (StringUtils.isEmpty(fpccArry)) {
                    log.warn("Find fpcc array from check vehicle invoice remote response failed");
                    code = ExceptionCode.SYSTEM_ERROR;
                    msg = "parse remote response failed";
                } else {
                    String firstFpcc = StringUtils.substring(fpccArry, 0, 3);
                    switch (firstFpcc) {
                        case "001":
                            code = "BUYER_NAME_NOT_MATCH";
                            msg = "wrong buyer_name";
                            break;
                        case "002":
                            code = "BUYER_CERT_NO_NOT_MATCH";
                            msg = "wrong buyer_cert_no";
                            break;
                        case "003":
                            code = "TAX_ID_NOT_MATCH";
                            msg = "wrong tax_id";
                            break;
                        case "004":
                            code = "VAT_INCLUSIVE_NOT_MATCH";
                            msg = "wrong vat_inclusive";
                            break;
                        case "005":
                            code = "ENGINE_NO_NOT_MATCH";
                            msg = "wrong engine_no";
                            break;
                        case "006":
                            code = "VIN_NOT_MATCH";
                            msg = "wrong vin";
                            break;
                        default:
                            log.warn("Find first fpcc from remote response failed");
                            code = ExceptionCode.SYSTEM_ERROR;
                            msg = "parse remote response failed";
                            break;
                    }
                }
                result.put("code", code);
                result.put("msg", msg);
            } else {
                log.error("Unexpect ztm: {}", ztm);
                throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "parse remote response error");
            }
            if ("EXIST".equals(bizResult)) {
                String kprq = findValueByPattern(response, kprqPattern, 1);
                invoiceDate = StringUtils.substring(kprq, 0, 10);

                address = findValueByPattern(response, swjgMcPattern, 1);
                kpse = findValueByPattern(response, kpsePattern, 1);
                bhsje = findValueByPattern(response, bhsjePattern, 1);
                xxfmc = findValueByPattern(response, xxfmcPattern, 1);
                zcdxzqh = findValueByPattern(response, zcdxzqhPattern, 1);
                skxzqh = findValueByPattern(response, skxzqhPattern, 1);
            }

            result.put("biz_result", bizResult);
            result.put("invoice_status", invoiceStatus);
            result.put("invoice_date", invoiceDate);
            result.put("adress", address);
            result.put("seller_address", getAddressByAreaNo(skxzqh));
            result.put("seller_name", xxfmc);
            result.put("tax_unit_area_address", getAddressByAreaNo(zcdxzqh));
            result.put("tax_excluded", bhsje);
            result.put("tax", kpse);
            return result;
        } catch (CodedRuntimeException e) {
            throw e;
        } catch (Exception e) {
            log.error("Parse check vehicle invoice remote response failed", e);
            throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "parse remote response error");
        }
    }

    protected String findValueByPattern(String content, Pattern pattern, int group) {
        Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            return matcher.group(group);
        } else {
            return null;
        }
    }

    protected String getAddressByAreaNo(String areaNo) {
        if (StringUtils.isEmpty(areaNo)) {
            return "";
        }
        return addressByAreaNo.get(areaNo);
    }

    protected String getToken() {
        if (isTokenNeedRefresh()) {
            return refreshToken(false);
        }
        return token;
    }

    protected boolean isTokenNeedRefresh() {
        return System.currentTimeMillis() >= tokenExpireAt || token == null;
    }

    protected void invalidateToken() {
        tokenExpireAt = 0;
    }

    @SuppressWarnings("unchecked")
    protected String refreshToken(boolean force) {
        synchronized (tokenRefreshLockObj) {
            if (!force && !isTokenNeedRefresh()) {
                return token;
            }

            try {
                HttpApiRequest httpApiRequest = new HttpApiRequest();
                httpApiRequest.setUri(HttpUrlUtils.buildUri(getTokenRemoteUrl, null));
                httpApiRequest.setMethod("POST");
                httpApiRequest.setHttpClientContext(httpClientContext);

                List<Header> headers = new ArrayList<>();
                if (StringUtils.isNotEmpty(mockHost)) {
                    headers.add(HttpClientUtils.h("Host", mockHost));
                }
                httpApiRequest.setHeaders(headers);

                List<NameValuePair> formParams = HttpClientUtils.nameValuePairsOf("grant_type", getTokenGrantType, "client_id",
                        getTokenClientId, "client_secret", getTokenClientSecret);
                httpApiRequest.setFormParams(formParams);

                String response = executeHttpApiRequest(httpApiRequest);
                Map<String, Object> responseJson = JSONUtils.parseMap(response);
                Map<String, Object> responseCustom = (Map<String, Object>) responseJson.get("custom");
                String newToken = (String) responseCustom.get("access_token");
                if (StringUtils.isEmpty(newToken)) {
                    log.error("Parse token failed: responseCustom={}", responseCustom);
                    throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "refresh token failed");
                }

                long expireSeconds = 1500L; // 默认1500s
                try {
                    String expireSecondsValue = (String) responseCustom.get("expires_in");
                    expireSeconds = Long.valueOf(expireSecondsValue, 10) - 300L; // 减去300s
                    expireSeconds = Math.max(60, expireSeconds);
                } catch (Exception e) {
                    log.error("Parse token expire seconds failed: responseCustom={}", responseCustom, e);
                }

                log.info("Refresh token: newToken={}, expireSeconds={}, oldToken={}",
                        new Object[] { newToken, expireSeconds, token });

                token = newToken;
                tokenExpireAt = System.currentTimeMillis() + expireSeconds * 1000;
                return token;
            } catch (CodedRuntimeException e) {
                throw e;
            } catch (Exception e) {
                log.error("Refresh token failed", e);
                throw new CodedRuntimeException(ExceptionCode.SYSTEM_ERROR, "refresh token failed");
            }
        }
    }

    protected boolean hasInvalidTokenError(String response) {
        return findValueByPattern(response, invalidTokenResponsePattern, 0) != null;
    }

    protected String executeHttpApiRequest(HttpApiRequest httpApiRequest) throws UnexpectedStatusCodeException, Exception {
        log.info("Remote request: request={}", httpApiRequest.toString());
        String responseText = execute(httpApiRequest);
        log.info("Remote response: response={}", responseText);
        return responseText;
    }

    protected String getRequiredStringParam(Map<String, Object> params, String paramName) {
        String value = (String) params.get(paramName);
        ParamCheckUtils.notEmptyString(value, paramName);
        return value;
    }

    // public static void main(String[] args) {
    // GxVehicleDataAdapterServiceImpl s = new
    // GxVehicleDataAdapterServiceImpl();
    // System.out.println(s.hasInvalidTokenError("{\"error_description\":\"The
    // access token is invalid\",\"error\":\"invalid_token\"}"));
    // System.out.println(s.hasInvalidTokenError("{\"error_description\":\"The
    // access token is invalid\",\"er1ror\":\"invalid_token\"}"));
    // }

}
